// Packaging/modules magic dance.
(function(factory) {
  factory(window.L)
}(function(L) {
  'use strict'

  L.Polyline._flat = L.LineUtil.isFlat || L.Polyline._flat || function(latlngs) {
  // true if it's a flat array of latlngs; false if nested
    return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined')
  }

  /**
* @fileOverview  Geometry utilities for distances and linear referencing.
* @name L.GeometryUtil
*/

  L.GeometryUtil = L.extend(L.GeometryUtil || {}, {

    /**
      Shortcut function for planar distance between two {L.LatLng} at current zoom.

      @tutorial distance-length

      @param {L.Map} map  map to be used for this method
      @param {L.LatLng} latlngA geographical point A
      @param {L.LatLng} latlngB geographical point B
      @returns {Number} planar distance
   */
    distance: function(map, latlngA, latlngB) {
      return map.latLngToLayerPoint(latlngA).distanceTo(map.latLngToLayerPoint(latlngB))
    },

    /**
      Shortcut function for planar distance between a {L.LatLng} and a segment (A-B).
      @param {L.Map} map  map to be used for this method
      @param {L.LatLng} latlng - The position to search
      @param {L.LatLng} latlngA geographical point A of the segment
      @param {L.LatLng} latlngB geographical point B of the segment
      @returns {Number} planar distance
  */
    distanceSegment: function(map, latlng, latlngA, latlngB) {
      var p = map.latLngToLayerPoint(latlng),
        p1 = map.latLngToLayerPoint(latlngA),
        p2 = map.latLngToLayerPoint(latlngB)
      return L.LineUtil.pointToSegmentDistance(p, p1, p2)
    },

    /**
      Shortcut function for converting distance to readable distance.
      @param {Number} distance distance to be converted
      @param {String} unit 'metric' or 'imperial'
      @returns {String} in yard or miles
  */
    readableDistance: function(distance, unit) {
      var isMetric = (unit !== 'imperial'),
        distanceStr
      if (isMetric) {
        // show metres when distance is < 1km, then show km
        if (distance > 1000) {
          distanceStr = (distance / 1000).toFixed(2) + ' km'
        } else {
          distanceStr = Math.ceil(distance) + ' m'
        }
      } else {
        distance *= 1.09361
        if (distance > 1760) {
          distanceStr = (distance / 1760).toFixed(2) + ' miles'
        } else {
          distanceStr = Math.ceil(distance) + ' yd'
        }
      }
      return distanceStr
    },

    /**
      Returns true if the latlng belongs to segment A-B
      @param {L.LatLng} latlng - The position to search
      @param {L.LatLng} latlngA geographical point A of the segment
      @param {L.LatLng} latlngB geographical point B of the segment
      @param {?Number} [tolerance=0.2] tolerance to accept if latlng belongs really
      @returns {boolean}
   */
    belongsSegment: function(latlng, latlngA, latlngB, tolerance) {
      tolerance = tolerance === undefined ? 0.2 : tolerance
      var hypotenuse = latlngA.distanceTo(latlngB),
        delta = latlngA.distanceTo(latlng) + latlng.distanceTo(latlngB) - hypotenuse
      return delta / hypotenuse < tolerance
    },

    /**
   * Returns total length of line
   * @tutorial distance-length
   *
   * @param {L.Polyline|Array<L.Point>|Array<L.LatLng>} coords Set of coordinates
   * @returns {Number} Total length (pixels for Point, meters for LatLng)
   */
    length: function(coords) {
      var accumulated = L.GeometryUtil.accumulatedLengths(coords)
      return accumulated.length > 0 ? accumulated[accumulated.length - 1] : 0
    },

    /**
   * Returns a list of accumulated length along a line.
   * @param {L.Polyline|Array<L.Point>|Array<L.LatLng>} coords Set of coordinates
   * @returns {Array<Number>} Array of accumulated lengths (pixels for Point, meters for LatLng)
   */
    accumulatedLengths: function(coords) {
      if (typeof coords.getLatLngs === 'function') {
        coords = coords.getLatLngs()
      }
      if (coords.length === 0)
        return []
      var total = 0,
        lengths = [0]
      for (var i = 0, n = coords.length - 1; i < n; i++) {
        total += coords[i].distanceTo(coords[i + 1])
        lengths.push(total)
      }
      return lengths
    },

    /**
      Returns the closest point of a {L.LatLng} on the segment (A-B)

      @tutorial closest

      @param {L.Map} map  map to be used for this method
      @param {L.LatLng} latlng - The position to search
      @param {L.LatLng} latlngA geographical point A of the segment
      @param {L.LatLng} latlngB geographical point B of the segment
      @returns {L.LatLng} Closest geographical point
  */
    closestOnSegment: function(map, latlng, latlngA, latlngB) {
      var maxzoom = map.getMaxZoom()
      if (maxzoom === Infinity)
        maxzoom = map.getZoom()
      var p = map.project(latlng, maxzoom),
        p1 = map.project(latlngA, maxzoom),
        p2 = map.project(latlngB, maxzoom),
        closest = L.LineUtil.closestPointOnSegment(p, p1, p2)
      return map.unproject(closest, maxzoom)
    },

    /**
      Returns the closest latlng on layer.

      Accept nested arrays

      @tutorial closest

      @param {L.Map} map  map to be used for this method
      @param {Array<L.LatLng>|Array<Array<L.LatLng>>|L.PolyLine|L.Polygon} layer - Layer that contains the result
      @param {L.LatLng} latlng - The position to search
      @param {?boolean} [vertices=false] - Whether to restrict to path vertices.
      @returns {L.LatLng} Closest geographical point or null if layer param is incorrect
  */
    closest: function(map, layer, latlng, vertices) {

      var latlngs,
        mindist = Infinity,
        result = null,
        i, n, distance, subResult

      if (layer instanceof Array) {
        // if layer is Array<Array<T>>
        if (layer[0] instanceof Array && typeof layer[0][0] !== 'number') {
          // if we have nested arrays, we calc the closest for each array
          // recursive
          for (i = 0; i < layer.length; i++) {
            subResult = L.GeometryUtil.closest(map, layer[i], latlng, vertices)
            if (subResult.distance < mindist) {
              mindist = subResult.distance
              result = subResult
            }
          }
          return result
        } else if (layer[0] instanceof L.LatLng ||
                      typeof layer[0][0] === 'number' ||
                      typeof layer[0].lat === 'number') { // we could have a latlng as [x,y] with x & y numbers or {lat, lng}
          layer = L.polyline(layer)
        } else {
          return result
        }
      }

      // if we don't have here a Polyline, that means layer is incorrect
      // see https://github.com/makinacorpus/.GeometryUtil/issues/23
      if (!(layer instanceof L.Polyline))
        return result

      // deep copy of latlngs
      latlngs = JSON.parse(JSON.stringify(layer.getLatLngs().slice(0)))

      // add the last segment for L.Polygon
      if (layer instanceof L.Polygon) {
        // add the last segment for each child that is a nested array
        var addLastSegment = function(latlngs) {
          if (L.Polyline._flat(latlngs)) {
            latlngs.push(latlngs[0])
          } else {
            for (var i = 0; i < latlngs.length; i++) {
              addLastSegment(latlngs[i])
            }
          }
        }
        addLastSegment(latlngs)
      }

      // we have a multi polygon / multi polyline / polygon with holes
      // use recursive to explore and return the good result
      if (!L.Polyline._flat(latlngs)) {
        for (i = 0; i < latlngs.length; i++) {
          // if we are at the lower level, and if we have a L.Polygon, we add the last segment
          subResult = L.GeometryUtil.closest(map, latlngs[i], latlng, vertices)
          if (subResult.distance < mindist) {
            mindist = subResult.distance
            result = subResult
          }
        }
        return result

      } else {

        // Lookup vertices
        if (vertices) {
          for (i = 0, n = latlngs.length; i < n; i++) {
            var ll = latlngs[i]
            distance = L.GeometryUtil.distance(map, latlng, ll)
            if (distance < mindist) {
              mindist = distance
              result = ll
              result.distance = distance
            }
          }
          return result
        }

        // Keep the closest point of all segments
        for (i = 0, n = latlngs.length; i < n - 1; i++) {
          var latlngA = latlngs[i],
            latlngB = latlngs[i + 1]
          distance = L.GeometryUtil.distanceSegment(map, latlng, latlngA, latlngB)
          if (distance <= mindist) {
            mindist = distance
            result = L.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB)
            result.distance = distance
          }
        }
        return result
      }

    },

    /**
      Returns the closest layer to latlng among a list of layers.

      @tutorial closest

      @param {L.Map} map  map to be used for this method
      @param {Array<L.ILayer>} layers Set of layers
      @param {L.LatLng} latlng - The position to search
      @returns {object} ``{layer, latlng, distance}`` or ``null`` if list is empty;
  */
    closestLayer: function(map, layers, latlng) {
      var mindist = Infinity,
        result = null,
        ll = null,
        distance = Infinity

      for (var i = 0, n = layers.length; i < n; i++) {
        var layer = layers[i]
        if (layer instanceof L.LayerGroup) {
          // recursive
          var subResult = L.GeometryUtil.closestLayer(map, layer.getLayers(), latlng)
          if (subResult.distance < mindist) {
            mindist = subResult.distance
            result = subResult
          }
        } else {
          // Single dimension, snap on points, else snap on closest
          if (typeof layer.getLatLng === 'function') {
            ll = layer.getLatLng()
            distance = L.GeometryUtil.distance(map, latlng, ll)
          } else {
            ll = L.GeometryUtil.closest(map, layer, latlng)
            if (ll) distance = ll.distance // Can return null if layer has no points.
          }
          if (distance < mindist) {
            mindist = distance
            result = { layer: layer, latlng: ll, distance: distance }
          }
        }
      }
      return result
    },

    /**
      Returns the n closest layers to latlng among a list of input layers.

      @param {L.Map} map -  map to be used for this method
      @param {Array<L.ILayer>} layers - Set of layers
      @param {L.LatLng} latlng - The position to search
      @param {?Number} [n=layers.length] - the expected number of output layers.
      @returns {Array<object>} an array of objects ``{layer, latlng, distance}`` or ``null`` if the input is invalid (empty list or negative n)
  */
    nClosestLayers: function(map, layers, latlng, n) {
      n = typeof n === 'number' ? n : layers.length

      if (n < 1 || layers.length < 1) {
        return null
      }

      var results = []
      var distance, ll

      for (var i = 0, m = layers.length; i < m; i++) {
        var layer = layers[i]
        if (layer instanceof L.LayerGroup) {
          // recursive
          var subResult = L.GeometryUtil.closestLayer(map, layer.getLayers(), latlng)
          results.push(subResult)
        } else {
          // Single dimension, snap on points, else snap on closest
          if (typeof layer.getLatLng === 'function') {
            ll = layer.getLatLng()
            distance = L.GeometryUtil.distance(map, latlng, ll)
          } else {
            ll = L.GeometryUtil.closest(map, layer, latlng)
            if (ll) distance = ll.distance // Can return null if layer has no points.
          }
          results.push({ layer: layer, latlng: ll, distance: distance })
        }
      }

      results.sort(function(a, b) {
        return a.distance - b.distance
      })

      if (results.length > n) {
        return results.slice(0, n)
      } else {
        return results
      }
    },

    /**
   * Returns all layers within a radius of the given position, in an ascending order of distance.
     @param {L.Map} map  map to be used for this method
     @param {Array<ILayer>} layers - A list of layers.
     @param {L.LatLng} latlng - The position to search
     @param {?Number} [radius=Infinity] - Search radius in pixels
     @return {object[]} an array of objects including layer within the radius, closest latlng, and distance
   */
    layersWithin: function(map, layers, latlng, radius) {
      radius = typeof radius === 'number' ? radius : Infinity

      var results = []
      var ll = null
      var distance = 0

      for (var i = 0, n = layers.length; i < n; i++) {
        var layer = layers[i]

        if (typeof layer.getLatLng === 'function') {
          ll = layer.getLatLng()
          distance = L.GeometryUtil.distance(map, latlng, ll)
        } else {
          ll = L.GeometryUtil.closest(map, layer, latlng)
          if (ll) distance = ll.distance // Can return null if layer has no points.
        }

        if (ll && distance < radius) {
          results.push({ layer: layer, latlng: ll, distance: distance })
        }
      }

      var sortedResults = results.sort(function(a, b) {
        return a.distance - b.distance
      })

      return sortedResults
    },

    /**
      Returns the closest position from specified {LatLng} among specified layers,
      with a maximum tolerance in pixels, providing snapping behaviour.

      @tutorial closest

      @param {L.Map} map  map to be used for this method
      @param {Array<ILayer>} layers - A list of layers to snap on.
      @param {L.LatLng} latlng - The position to snap
      @param {?Number} [tolerance=Infinity] - Maximum number of pixels.
      @param {?boolean} [withVertices=true] - Snap to layers vertices or segment points (not only vertex)
      @returns {object} with snapped {LatLng} and snapped {Layer} or null if tolerance exceeded.
  */
    closestLayerSnap: function(map, layers, latlng, tolerance, withVertices) {
      tolerance = typeof tolerance === 'number' ? tolerance : Infinity
      withVertices = typeof withVertices === 'boolean' ? withVertices : true

      var result = L.GeometryUtil.closestLayer(map, layers, latlng)
      if (!result || result.distance > tolerance)
        return null

      // If snapped layer is linear, try to snap on vertices (extremities and middle points)
      if (withVertices && typeof result.layer.getLatLngs === 'function') {
        var closest = L.GeometryUtil.closest(map, result.layer, result.latlng, true)
        if (closest.distance < tolerance) {
          result.latlng = closest
          result.distance = L.GeometryUtil.distance(map, closest, latlng)
        }
      }
      return result
    },

    /**
      Returns the Point located on a segment at the specified ratio of the segment length.
      @param {L.Point} pA coordinates of point A
      @param {L.Point} pB coordinates of point B
      @param {Number} the length ratio, expressed as a decimal between 0 and 1, inclusive.
      @returns {L.Point} the interpolated point.
  */
    interpolateOnPointSegment: function(pA, pB, ratio) {
      return L.point(
        (pA.x * (1 - ratio)) + (ratio * pB.x),
        (pA.y * (1 - ratio)) + (ratio * pB.y)
      )
    },

    /**
      Returns the coordinate of the point located on a line at the specified ratio of the line length.
      @param {L.Map} map  map to be used for this method
      @param {Array<L.LatLng>|L.PolyLine} latlngs Set of geographical points
      @param {Number} ratio the length ratio, expressed as a decimal between 0 and 1, inclusive
      @returns {Object} an object with latLng ({LatLng}) and predecessor ({Number}), the index of the preceding vertex in the Polyline
      (-1 if the interpolated point is the first vertex)
  */
    interpolateOnLine: function(map, latLngs, ratio) {
      latLngs = (latLngs instanceof L.Polyline) ? latLngs.getLatLngs() : latLngs
      var n = latLngs.length
      if (n < 2) {
        return null
      }

      // ensure the ratio is between 0 and 1;
      ratio = Math.max(Math.min(ratio, 1), 0)

      if (ratio === 0) {
        return {
          latLng: latLngs[0] instanceof L.LatLng ? latLngs[0] : L.latLng(latLngs[0]),
          predecessor: -1
        }
      }
      if (ratio == 1) {
        return {
          latLng: latLngs[latLngs.length - 1] instanceof L.LatLng ? latLngs[latLngs.length - 1] : L.latLng(latLngs[latLngs.length - 1]),
          predecessor: latLngs.length - 2
        }
      }

      // project the LatLngs as Points,
      // and compute total planar length of the line at max precision
      var maxzoom = map.getMaxZoom()
      if (maxzoom === Infinity)
        maxzoom = map.getZoom()
      var pts = []
      var lineLength = 0
      for (var i = 0; i < n; i++) {
        pts[i] = map.project(latLngs[i], maxzoom)
        if (i > 0)
          lineLength += pts[i - 1].distanceTo(pts[i])
      }

      var ratioDist = lineLength * ratio

      // follow the line segments [ab], adding lengths,
      // until we find the segment where the points should lie on
      var cumulativeDistanceToA = 0, cumulativeDistanceToB = 0
      for (var i = 0; cumulativeDistanceToB < ratioDist; i++) {
        var pointA = pts[i], pointB = pts[i + 1]

        cumulativeDistanceToA = cumulativeDistanceToB
        cumulativeDistanceToB += pointA.distanceTo(pointB)
      }
  
      if (pointA == undefined && pointB == undefined) { // Happens when line has no length
        var pointA = pts[0], pointB = pts[1], i = 1
      }

      // compute the ratio relative to the segment [ab]
      var segmentRatio = ((cumulativeDistanceToB - cumulativeDistanceToA) !== 0) ? ((ratioDist - cumulativeDistanceToA) / (cumulativeDistanceToB - cumulativeDistanceToA)) : 0
      var interpolatedPoint = L.GeometryUtil.interpolateOnPointSegment(pointA, pointB, segmentRatio)
      return {
        latLng: map.unproject(interpolatedPoint, maxzoom),
        predecessor: i - 1
      }
    },

    /**
      Returns a float between 0 and 1 representing the location of the
      closest point on polyline to the given latlng, as a fraction of total line length.
      (opposite of L.GeometryUtil.interpolateOnLine())
      @param {L.Map} map  map to be used for this method
      @param {L.PolyLine} polyline Polyline on which the latlng will be search
      @param {L.LatLng} latlng The position to search
      @returns {Number} Float between 0 and 1
  */
    locateOnLine: function(map, polyline, latlng) {
      var latlngs = polyline.getLatLngs()
      if (latlng.equals(latlngs[0]))
        return 0.0
      if (latlng.equals(latlngs[latlngs.length - 1]))
        return 1.0

      var point = L.GeometryUtil.closest(map, polyline, latlng, false),
        lengths = L.GeometryUtil.accumulatedLengths(latlngs),
        total_length = lengths[lengths.length - 1],
        portion = 0,
        found = false
      for (var i = 0, n = latlngs.length - 1; i < n; i++) {
        var l1 = latlngs[i],
          l2 = latlngs[i + 1]
        portion = lengths[i]
        if (L.GeometryUtil.belongsSegment(point, l1, l2, 0.0001)) {
          portion += l1.distanceTo(point)
          found = true
          break
        }
      }
      if (!found) {
        throw 'Could not interpolate ' + latlng.toString() + ' within ' + polyline.toString()
      }
      return portion / total_length
    },

    /**
      Returns a clone with reversed coordinates.
      @param {L.PolyLine} polyline polyline to reverse
      @returns {L.PolyLine} polyline reversed
  */
    reverse: function(polyline) {
      return L.polyline(polyline.getLatLngs().slice(0).reverse())
    },

    /**
      Returns a sub-part of the polyline, from start to end.
      If start is superior to end, returns extraction from inverted line.
      @param {L.Map} map  map to be used for this method
      @param {L.PolyLine} polyline Polyline on which will be extracted the sub-part
      @param {Number} start ratio, expressed as a decimal between 0 and 1, inclusive
      @param {Number} end ratio, expressed as a decimal between 0 and 1, inclusive
      @returns {Array<L.LatLng>} new polyline
   */
    extract: function(map, polyline, start, end) {
      if (start > end) {
        return L.GeometryUtil.extract(map, L.GeometryUtil.reverse(polyline), 1.0 - start, 1.0 - end)
      }

      // Bound start and end to [0-1]
      start = Math.max(Math.min(start, 1), 0)
      end = Math.max(Math.min(end, 1), 0)

      var latlngs = polyline.getLatLngs(),
        startpoint = L.GeometryUtil.interpolateOnLine(map, polyline, start),
        endpoint = L.GeometryUtil.interpolateOnLine(map, polyline, end)
      // Return single point if start == end
      if (start == end) {
        var point = L.GeometryUtil.interpolateOnLine(map, polyline, end)
        return [point.latLng]
      }
      // Array.slice() works indexes at 0
      if (startpoint.predecessor == -1)
        startpoint.predecessor = 0
      if (endpoint.predecessor == -1)
        endpoint.predecessor = 0
      var result = latlngs.slice(startpoint.predecessor + 1, endpoint.predecessor + 1)
      result.unshift(startpoint.latLng)
      result.push(endpoint.latLng)
      return result
    },

    /**
      Returns true if first polyline ends where other second starts.
      @param {L.PolyLine} polyline First polyline
      @param {L.PolyLine} other Second polyline
      @returns {bool}
  */
    isBefore: function(polyline, other) {
      if (!other) return false
      var lla = polyline.getLatLngs(),
        llb = other.getLatLngs()
      return (lla[lla.length - 1]).equals(llb[0])
    },

    /**
      Returns true if first polyline starts where second ends.
      @param {L.PolyLine} polyline First polyline
      @param {L.PolyLine} other Second polyline
      @returns {bool}
  */
    isAfter: function(polyline, other) {
      if (!other) return false
      var lla = polyline.getLatLngs(),
        llb = other.getLatLngs()
      return (lla[0]).equals(llb[llb.length - 1])
    },

    /**
      Returns true if first polyline starts where second ends or start.
      @param {L.PolyLine} polyline First polyline
      @param {L.PolyLine} other Second polyline
      @returns {bool}
  */
    startsAtExtremity: function(polyline, other) {
      if (!other) return false
      var lla = polyline.getLatLngs(),
        llb = other.getLatLngs(),
        start = lla[0]
      return start.equals(llb[0]) || start.equals(llb[llb.length - 1])
    },

    /**
      Returns horizontal angle in degres between two points.
      @param {L.Point} a Coordinates of point A
      @param {L.Point} b Coordinates of point B
      @returns {Number} horizontal angle
   */
    computeAngle: function(a, b) {
      return (Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI)
    },

    /**
     Returns slope (Ax+B) between two points.
      @param {L.Point} a Coordinates of point A
      @param {L.Point} b Coordinates of point B
      @returns {Object} with ``a`` and ``b`` properties.
   */
    computeSlope: function(a, b) {
      var s = (b.y - a.y) / (b.x - a.x),
        o = a.y - (s * a.x)
      return { 'a': s, 'b': o }
    },

    /**
     Returns LatLng of rotated point around specified LatLng center.
      @param {L.LatLng} latlngPoint: point to rotate
      @param {double} angleDeg: angle to rotate in degrees
      @param {L.LatLng} latlngCenter: center of rotation
      @returns {L.LatLng} rotated point
   */
    rotatePoint: function(map, latlngPoint, angleDeg, latlngCenter) {
      var maxzoom = map.getMaxZoom()
      if (maxzoom === Infinity)
        maxzoom = map.getZoom()
      var angleRad = angleDeg * Math.PI / 180,
        pPoint = map.project(latlngPoint, maxzoom),
        pCenter = map.project(latlngCenter, maxzoom),
        x2 = Math.cos(angleRad) * (pPoint.x - pCenter.x) - Math.sin(angleRad) * (pPoint.y - pCenter.y) + pCenter.x,
        y2 = Math.sin(angleRad) * (pPoint.x - pCenter.x) + Math.cos(angleRad) * (pPoint.y - pCenter.y) + pCenter.y
      return map.unproject(new L.Point(x2, y2), maxzoom)
    },

    /**
     Returns the bearing in degrees clockwise from north (0 degrees)
     from the first L.LatLng to the second, at the first LatLng
     @param {L.LatLng} latlng1: origin point of the bearing
     @param {L.LatLng} latlng2: destination point of the bearing
     @returns {float} degrees clockwise from north.
  */
    bearing: function(latlng1, latlng2) {
      var rad = Math.PI / 180,
        lat1 = latlng1.lat * rad,
        lat2 = latlng2.lat * rad,
        lon1 = latlng1.lng * rad,
        lon2 = latlng2.lng * rad,
        y = Math.sin(lon2 - lon1) * Math.cos(lat2),
        x = Math.cos(lat1) * Math.sin(lat2) -
              Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1)

      var bearing = ((Math.atan2(y, x) * 180 / Math.PI) + 360) % 360
      return bearing >= 180 ? bearing - 360 : bearing
    },

    /**
     Returns the point that is a distance and heading away from
     the given origin point.
     @param {L.LatLng} latlng: origin point
     @param {float} heading: heading in degrees, clockwise from 0 degrees north.
     @param {float} distance: distance in meters
     @returns {L.latLng} the destination point.
     Many thanks to Chris Veness at http://www.movable-type.co.uk/scripts/latlong.html
     for a great reference and examples.
  */
    destination: function(latlng, heading, distance) {
      heading = (heading + 360) % 360
      var rad = Math.PI / 180,
        radInv = 180 / Math.PI,
        R = 6378137, // approximation of Earth's radius
        lon1 = latlng.lng * rad,
        lat1 = latlng.lat * rad,
        rheading = heading * rad,
        sinLat1 = Math.sin(lat1),
        cosLat1 = Math.cos(lat1),
        cosDistR = Math.cos(distance / R),
        sinDistR = Math.sin(distance / R),
        lat2 = Math.asin(sinLat1 * cosDistR + cosLat1 *
              sinDistR * Math.cos(rheading)),
        lon2 = lon1 + Math.atan2(Math.sin(rheading) * sinDistR *
              cosLat1, cosDistR - sinLat1 * Math.sin(lat2))
      lon2 = lon2 * radInv
      lon2 = lon2 > 180 ? lon2 - 360 : lon2 < -180 ? lon2 + 360 : lon2
      return L.latLng([lat2 * radInv, lon2])
    },

    /**
     Returns the the angle of the given segment and the Equator in degrees,
     clockwise from 0 degrees north.
     @param {L.Map} map:  map to be used for this method
     @param {L.LatLng} latlngA: geographical point A of the segment
     @param {L.LatLng} latlngB: geographical point B of the segment
     @returns {Float} the angle in degrees.
  */
    angle: function(map, latlngA, latlngB) {
      var pointA = map.latLngToContainerPoint(latlngA),
        pointB = map.latLngToContainerPoint(latlngB),
        angleDeg = Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) * 180 / Math.PI + 90
      angleDeg += angleDeg < 0 ? 360 : 0
      return angleDeg
    },

    /**
     Returns a point snaps on the segment and heading away from the given origin point a distance.
     @param {L.Map} map:  map to be used for this method
     @param {L.LatLng} latlngA: geographical point A of the segment
     @param {L.LatLng} latlngB: geographical point B of the segment
     @param {float} distance: distance in meters
     @returns {L.latLng} the destination point.
  */
    destinationOnSegment: function(map, latlngA, latlngB, distance) {
      var angleDeg = L.GeometryUtil.angle(map, latlngA, latlngB),
        latlng = L.GeometryUtil.destination(latlngA, angleDeg, distance)
      return L.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB)
    }
  })

  return L.GeometryUtil

}))
